package com.kaitian.demo.demo_boot_starter.service.impl;

import com.kaitian.demo.demo_boot_starter.service.IRedisService;
import com.kaitian.demo.demo_boot_starter.utils.RedisUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * @author ：xietian
 * @version ：V1.0
 * @program ：demo
 * @date ：Created in 2022/4/8 15:49
 * @description ：
 */
@Slf4j
@Service
public class RedisServiceImpl implements IRedisService {

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private RedisUtil redisUtil;

    private Object lock = new Object();

    /**
     * 测试Redis事务
     */
    @Override
    public void testRedisTrancation() {

        // 测试事务机制
        redisTemplate.opsForValue().set("key1", 1);
        List list = (List) redisTemplate.execute((RedisOperations operations) -> {
            // 设置要监控Key1
            operations.watch("key1");
            // 开启事务，再exec命令执行前，全部都只是进入队列
            operations.multi();
            operations.opsForValue().set("key2", "value2");
            operations.opsForValue().increment("key1", 1);
            System.out.println(operations.opsForValue().get("key2"));

            return operations.exec();
        });

        System.out.println(list);
    }

    /**
     * 测试Redis流水线
     */
    @Override
    public void testRedisPipline() {
        Long start = System.currentTimeMillis();

        List list = redisTemplate.executePipelined((RedisOperations operations) -> {
           for (int i = 1; i <= 10000; i++) {
               operations.opsForValue().set("pipeline:" + i, i);
               // 命令只是进入队列，此处获取不到数据
//               System.out.println(operations.opsForValue().get("pipline:" + i));
           }
           return null;
        });
        Long end = System.currentTimeMillis();

        //  测试结果，1w数据存储 2397ms， 非常适合大数据量的操作，比非流水的速度为x10倍
        System.out.println("耗时：" + (end - start) + " ms");
//        System.out.println(list);
    }

    /**
     * 测试Redis 消息订阅
     */
    @PostConstruct
    @Override
    public void testRedisMessage() {
        Long start = System.currentTimeMillis();

        redisTemplate.convertAndSend("redis_message", "xxxxx");
        Long end = System.currentTimeMillis();

        //  测试结果，1w数据存储 2397ms， 非常适合大数据量的操作，比非流水的速度为x10倍
        System.out.println("耗时：" + (end - start) + " ms");
//        System.out.println(list);
    }


    /**
     * Redis分布式锁实现超卖
     */
    @Override
    public void testRedisOverSale() {

    }

    /**
     * 初始化商品库存信息
     */
    @Override
    public void initProduct(String key, Integer num) {
        redisTemplate.opsForValue().set(key, num);
    }

    /**
     * 不使用锁，百分百出现超卖
     * @param i
     */
    @Async
    @Override
    public Integer buyProduct0(String key, Integer i) {
        Integer num = (Integer) redisTemplate.opsForValue().get(key);
        if (num > 0 && num >= i) {
            // 不使用锁
            log.info("剩余{}，本次购买：{}", num, i);
            redisTemplate.opsForValue().decrement(key, i);
            return i;
        }
        return 0;
    }

    /**
     * 不使用锁，百分百出现超卖
     * @param i
     */
    @Async
    @Override
    public Integer buyProduct1(String key, Integer i) {
        Integer num = (Integer) redisTemplate.boundValueOps(key).get();
        if (num > 0 && num >= i) {
            // 不使用锁
            log.info("剩余{}，本次购买：{}", num, i);
            redisTemplate.boundValueOps(key).decrement(i);
            return i;
        }
        return 0;
    }

    /**
     * 方法上使用synchronized关键字，解决了超卖问题
     * @param i
     */
    @Async
    @Override
    public synchronized Integer buyProduct2(String key, Integer i) {
        Integer num = (Integer) redisTemplate.opsForValue().get(key);
        if (num > 0 && num >= i) {
            // 不使用锁
            log.info("剩余{}，本次购买：{}", num, i);
            redisTemplate.opsForValue().decrement(key, i);
            return i;
        }
        return 0;
    }

    /**
     * 部分代码上使用synchronized关键字，解决了超卖问题
     * synchronize（this）作用于当前对象，保证同一时间只有一个线程能调用该对象。
     * synchronize（*.class）作用于该类所属的所有实例，保证同一时间只有一个线程能调用该类的实例。
     * synchronize（lock）作用于当前代码块，保证只有一个线程能调用该部分代码。
     * @param i
     */
    @Async
    @Override
    public Integer buyProduct3(String key, Integer i) {
        synchronized(this) {
//        synchronized(lock) {
            Integer num = (Integer) redisTemplate.opsForValue().get(key);
            if (num > 0 && num >= i) {
                // 不使用锁
                log.info("剩余{}，本次购买：{}", num, i);
                redisTemplate.opsForValue().decrement(key, i);
                return i;
            }
        }
        return 0;
    }

    /**
     * 双重校验所，未解决超卖问题
     * @param i
     */
    @Async
    @Override
    public Integer buyProduct4(String key, Integer i) {
        Integer num = (Integer) redisTemplate.opsForValue().get(key);
        if (num > 0 && num >= i) {
            // redis双层校验
            synchronized (num) {
                num = (Integer) redisTemplate.opsForValue().get(key);
                if (num > 0 && num >= i) {
                    log.info("剩余{}，本次购买：{}", num, i);
                    redisTemplate.opsForValue().decrement(key, i);
                    return i;
                }
            }
        }
        return 0;
    }

    /**
     * 使用Redis的分布式锁，进行处理； 可以解决超卖问题，但是性能并不是很快； 原理和线程同步锁一致，在同一时间，允许一个人操作
     * @param i
     */
    @Override
    public Integer buyProduct5(String key, Integer i) {
        // 加锁
        Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", "locked", 1, TimeUnit.SECONDS);
        if (lock) {
            log.info("成功获取锁");
            Integer num = (Integer) redisTemplate.opsForValue().get(key);
            // 成功取得锁并满足操作旧进行业务
            if (num > 0 && num >= i) {
                log.info("剩余{}，本次购买：{}", num, i);
                redisTemplate.opsForValue().decrement(key, i);
                // 释放锁
                redisTemplate.delete("lock");
                return i;
            } else if (num <= 0) {
                // 释放锁
                redisTemplate.delete("lock");
            }
        }
        return 0;
    }

    /**
     * 使用Redis的事务，watch乐观锁
     * @param i
     */
    @Override
    public Integer buyProduct6(String key, Integer i) {
        // 事务机制
        Object lock = redisTemplate.execute((RedisOperations operations) -> {
            // 设置要监控Key1
            operations.watch(key);// 如果watch后面的值没有发生变化则生效，变化了则不生效
            Integer num = (Integer) operations.opsForValue().get(key);
            if (num > 0 && num >= i) {
                // 开启事务，再exec命令执行前，全部都只是进入队列
                operations.multi();

                log.info("剩余{}，本次购买：{}", num, i);
                redisTemplate.opsForValue().decrement(key, i);

                List exec = operations.exec();

                if (null != exec && exec.size() > 0) {
                    return i;
                }
                return 0;
            }

            return 0;
        });

        return (Integer) lock;
    }


}